Categories
React

React Testing — Mocking Modules and Dispatching Events

Spread the love

Automated tests are important for most apps.

In this article, we’ll take a look at how to write tests for React components.

Mocking Modules

We can mock modules that don’t work well in a test environment.

For example, if we have the following components:

Map.js

import React from "react";

import { LoadScript, GoogleMap } from "react-google-maps";
export default function Map(props) {
  return (
    <LoadScript id="script-loader" googleMapsApiKey="YOUR_API_KEY">
      <GoogleMap id="example-map" center={props.center} />
    </LoadScript>
  );
}

Contact.js

import Map from "./map";

export default function Contact({ name, email }) {
  return (
    <div>
      <address>
        {name} {email}
      </address>
      <Map center={props.center} />
    </div>
  );
}

Then we can test the Contact component with a mocked Map component by writing:

Contact.test.js

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";

import Contact from "./contact";

jest.mock("./map", () => {
  return function DummyMap(props) {
    return (
      <div data-testid="map">
        {props.center.lat}:{props.center.long}
      </div>
    );
  };
});

let container = null;
beforeEach(() => {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

it("should render contact information", () => {
  const center = { lat: 0, long: 0 };
  act(() => {
    render(
      <Contact
        name="james"
        email="test@example.com"
        center={center}
      />,
      container
    );
  });

  expect(
    container.querySelector("address").textContent
  )
    .toContain("james test@example.com");

  expect(container.querySelector('[data-testid="map"]').textContent).toEqual(
    "0:0"
  );
});

We have:

jest.mock("./map", () => {
  return function DummyMap(props) {
    return (
      <div data-testid="map">
        {props.center.lat}:{props.center.long}
      </div>
    );
  };
});

to mock the Map component.

Then when we call render , we render with DummyMap instead of the actual Map component.

Events

To test events, we can dispatch real DOM events on DOM elements.

For instance, if we want to test the Toggle component:

import React, { useState } from "react";

export default function Toggle(props) {
  const [state, setState] = useState(false);
  return (
    <button
      onClick={() => {
        setState(previousState => !previousState);
        props.onChange(!state);
      }}
      data-testid="toggle"
    >
      {state ? "off" : "on"}
    </button>
  );
}

Then we can add a test file for it by writing:

Toggle.test.js

import React from "react";
import { render, unmountComponentAtNode } from "react-dom";
import { act } from "react-dom/test-utils";

import Toggle from "./toggle";

let container = null;
beforeEach(() => {
  container = document.createElement("div");
  document.body.appendChild(container);
});

afterEach(() => {
  unmountComponentAtNode(container);
  container.remove();
  container = null;
});

it("changes value when clicked", () => {
  const onChange = jest.fn();
  act(() => {
    render(<Toggle onChange={onChange} />, container);
  });
  const button = document.querySelector("[data-testid=toggle]");
  expect(button.innerHTML).toBe("on");
  act(() => {
    button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
  });
  expect(onChange).toHaveBeenCalledTimes(1);
  expect(button.innerHTML).toBe("off");
  act(() => {
    button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
  });
  expect(onChange).toHaveBeenCalledTimes(2);
  expect(button.innerHTML).toBe("on");
});

We mock the onChange method that we pass in as the value of the onChange prop.

Then we get the button with the selector[data-testid=toggle] from the Toggle component.

Then we can get the content of the button and how many times onChange has been called after we call dispatchEvent to dispatch a click MouseEvent .

We need to pass in { bubbles: true } so that React will delegate the event to the document.

Conclusion

We can mock modules that we can’t use conveniently in our tests.

Also, we can trigger events on elements and check the result after that.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *